package drds.binlog.binlog_event.event;

import drds.binlog.binlog_event.LogByteBlock;
import drds.binlog.binlog_event.event.unimportance.StartLogEventV3;
import drds.binlog.common.Authors;
import drds.common.Author;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

/**
 * For binlog version 4. This event is saved by threads which read it, as they
 * need it for future use (to decode the ordinary events).
 *
 * @see mysql-5.1.60/sql/log_event.cc - Format_description_log_event
 */
@Author(name = Authors.LI_YANG)
@Slf4j
public final class FormatDescriptionLogEvent extends StartLogEventV3
{

    /**
     * the number of types we handle in format_description_log_event
     * (unknown_event is not to be handled, it does not exist in binlogs, it
     * does not have a format).
     */
    public static final int log_event_types = (enum_end_event - 1);

    public static final int st_common_header_len_offset = (st_server_ver_offset + st_server_ver_len + 4);

    public static final int old_header_len = 13;
    public static final int log_event_header_len = 19;
    public static final int log_event_minimal_header_len = 19;

    /* event-specific post-headerpacket sizes */
    public static final int stop_header_len = 0;
    public static final int load_header_length = (4 + 4 + 4 + 1 + 1 + 4);
    public static final int slave_header_len = 0;
    public static final int start_v3_header_len = (2 + st_server_ver_len + 4);
    public static final int rotate_header_len = 8;                                                       // this
    // is
    // frozen
    // (the
    // rotate
    // post-headerpacket
    // is
    // frozen)
    public static final int intvar_header_len = 0;
    public static final int create_file_header_length = 4;
    public static final int append_block_header_len = 4;
    public static final int exec_load_header_len = 4;
    public static final int delete_file_header_len = 4;
    public static final int new_load_header_len = load_header_length;
    public static final int rand_header_len = 0;
    public static final int user_var_header_len = 0;
    public static final int format_description_header_len = (start_v3_header_len + 1 + log_event_types);
    public static final int xid_header_len = 0;
    public static final int begin_load_query_header_len = append_block_header_len;
    public static final int rows_header_len_v1 = 8;
    public static final int table_map_header_len = 8;
    public static final int execute_load_query_extra_header_len = (4 + 4 + 4 + 1);
    public static final int execute_load_query_header_len = (query_header_len + execute_load_query_extra_header_len);
    public static final int incident_header_len = 2;
    public static final int heartbeat_header_len = 0;
    public static final int ignorable_header_len = 0;
    public static final int rows_header_len_v2 = 10;
    public static final int transaction_context_header_len = 18;
    public static final int view_change_header_len = 52;
    public static final int xa_prepare_header_len = 0;

    public static final int annotate_rows_header_len = 0;
    public static final int binlog_checkpoint_header_len = 4;
    public static final int gtid_header_len = 19;
    public static final int gtid_list_header_len = 4;
    public static final int start_encryption_header_len = 0;

    public static final int post_header_length = 11;

    public static final int binlog_checksum_alg_desc_len = 1;
    public static final int[] checksumVersionSplit = {5, 6, 1};
    public static final long checksumVersionProduct = (checksumVersionSplit[0] * 256 + checksumVersionSplit[1])
            * 256 + checksumVersionSplit[2];
    /**
     * MySQL 5.0 format descriptions.
     */
    public static final FormatDescriptionLogEvent format_description_event_5_x = new FormatDescriptionLogEvent(4);
    /**
     * MySQL 4.0.x (x>=2) format descriptions.
     */
    public static final FormatDescriptionLogEvent format_description_event_4_0_x = new FormatDescriptionLogEvent(3);
    /**
     * MySQL 3.23 format descriptions.
     */
    public static final FormatDescriptionLogEvent format_description_event_3_23 = new FormatDescriptionLogEvent(1);
    /**
     * The size of the fixed headerPacket which _all_ events have (for binlogs written
     * by this version, this is equal to LOG_EVENT_HEADER_LEN), except
     * FORMAT_DESCRIPTION_EVENT and ROTATE_EVENT (those have a headerPacket of size
     * LOG_EVENT_MINIMAL_HEADER_LEN).
     */
    public final int commonHeaderLength;
    /**
     * The list of post-headers' lengthes
     */
    public final short[] postHeaderLengths;
    public int numberOfEventTypes;
    public int[] serverVersionSplit = new int[3];

    public FormatDescriptionLogEvent(LogHeader logHeader, LogByteBlock logByteBlock, FormatDescriptionLogEvent formatDescriptionLogEvent)
            throws IOException
    {
        /* Start_log_event_v3 */
        super(logHeader, logByteBlock, formatDescriptionLogEvent);

        logByteBlock.setIndexAsOriginIndexAndOffset(log_event_minimal_header_len + st_common_header_len_offset);
        commonHeaderLength = logByteBlock.get1UnsignedIntByIndex();
        if (commonHeaderLength < old_header_len) /* sanity check */
        {
            throw new IOException("Format Description event headerPacket length is too short");
        }

        numberOfEventTypes = logByteBlock.limit() - (log_event_minimal_header_len + st_common_header_len_offset + 1);

        // bytes.setIndexAsOriginIndexAndOffset$originIndex(LOG_EVENT_MINIMAL_HEADER_LEN
        // + ST_COMMON_HEADER_LEN_OFFSET + 1);
        postHeaderLengths = new short[numberOfEventTypes];
        for (int i = 0; i < numberOfEventTypes; i++)
        {
            postHeaderLengths[i] = (short) logByteBlock.get1UnsignedIntByIndex();
        }

        calcServerVersionSplit();
        long versionProduct = getVersionProduct();
        if (versionProduct >= checksumVersionProduct)
        {
            /*
             * the last bytes are the checksum alg desc and value (or value's
             * room)
             */
            numberOfEventTypes -= binlog_checksum_alg_desc_len;
        }

        if (log.isInfoEnabled()) log.info("common_header_len= " + commonHeaderLength + ", number_of_event_types= "
                + numberOfEventTypes);
    }

    public FormatDescriptionLogEvent(final int binlogVersion, int binlogChecksum)
    {
        this(binlogVersion);
        this.logHeader.checksumAlg = binlogChecksum;
    }

    public FormatDescriptionLogEvent(final int binlogVersion)
    {
        this.binlogVersion = binlogVersion;

        postHeaderLengths = new short[enum_end_event];
        /* identify binlog format */
        switch (binlogVersion)
        {
            case 4: /* MySQL 5.0 */
                serverVersion = server_version;
                commonHeaderLength = log_event_header_len;
                numberOfEventTypes = log_event_types;

                /*
                 * Note: all event types must explicitly fill in their lengths
                 * here.
                 */
                postHeaderLengths[start_event_v3 - 1] = start_v3_header_len;
                postHeaderLengths[query_event - 1] = query_header_len;
                postHeaderLengths[stop_event - 1] = stop_header_len;
                postHeaderLengths[rotate_event - 1] = rotate_header_len;
                postHeaderLengths[intvar_event - 1] = intvar_header_len;
                postHeaderLengths[load_event - 1] = load_header_length;
                postHeaderLengths[slave_event - 1] = slave_header_len;
                postHeaderLengths[create_file_event - 1] = create_file_header_length;
                postHeaderLengths[append_block_event - 1] = append_block_header_len;
                postHeaderLengths[exec_load_event - 1] = exec_load_header_len;
                postHeaderLengths[delete_file_event - 1] = delete_file_header_len;
                postHeaderLengths[new_load_event - 1] = new_load_header_len;
                postHeaderLengths[rand_event - 1] = rand_header_len;
                postHeaderLengths[user_var_event - 1] = user_var_header_len;
                postHeaderLengths[format_description_event - 1] = format_description_header_len;
                postHeaderLengths[xid_event - 1] = xid_header_len;
                postHeaderLengths[begin_load_query_event - 1] = begin_load_query_header_len;
                postHeaderLengths[execute_load_query_event - 1] = execute_load_query_header_len;
                postHeaderLengths[table_map_event - 1] = table_map_header_len;
                postHeaderLengths[write_rows_event_v1 - 1] = rows_header_len_v1;
                postHeaderLengths[update_rows_event_v1 - 1] = rows_header_len_v1;
                postHeaderLengths[delete_rows_event_v1 - 1] = rows_header_len_v1;
                /*
                 * We here have the possibility to simulate a master of before
                 * we changed the table map id to be stored in 6 bytes: createDateTimeLong it
                 * was stored in 4 bytes (=> post_header_len was 6). This is
                 * used to test backward compatibility. This code can be removed
                 * after a few months (today is Dec 21st 2005), createDateTimeLong we know
                 * that the 4-byte masters are not deployed anymore (check with
                 * Tomas Ulin first!), and the accompanying test
                 * (rpl_row_4_bytes) too.
                 */
                postHeaderLengths[heartbeat_log_event - 1] = 0;
                postHeaderLengths[ignorable_log_event - 1] = ignorable_header_len;
                postHeaderLengths[rows_query_log_event - 1] = ignorable_header_len;
                postHeaderLengths[write_rows_event - 1] = rows_header_len_v2;
                postHeaderLengths[update_rows_event - 1] = rows_header_len_v2;
                postHeaderLengths[delete_rows_event - 1] = rows_header_len_v2;
                postHeaderLengths[gtid_log_event - 1] = post_header_length;
                postHeaderLengths[anonymous_gtid_log_event - 1] = post_header_length;
                postHeaderLengths[previous_gtids_log_event - 1] = ignorable_header_len;

                postHeaderLengths[transaction_context_event - 1] = transaction_context_header_len;
                postHeaderLengths[view_change_event - 1] = view_change_header_len;
                postHeaderLengths[xa_prepare_log_event - 1] = xa_prepare_header_len;

                // mariadb 10
                postHeaderLengths[annotate_rows_event - 1] = annotate_rows_header_len;
                postHeaderLengths[binlog_checkpoint_event - 1] = binlog_checkpoint_header_len;
                postHeaderLengths[gtid_event - 1] = gtid_header_len;
                postHeaderLengths[gtid_list_event - 1] = gtid_list_header_len;
                postHeaderLengths[start_encryption_event - 1] = start_encryption_header_len;
                break;

            case 3: /* 4.0.x x>=2 */
                /*
                 * We build an artificial (i.e. not sent by the master) event,
                 * which describes what those old master versions send.
                 */
                serverVersion = "4.0";
                commonHeaderLength = log_event_minimal_header_len;

                /*
                 * The first new event in binlog version 4 is Format_desc. So
                 * any event logType after that does not exist in older versions.
                 * We use the events known by version 3, even if version 1 had
                 * only a subset of them (this is not a problem: it uses a few
                 * bytes for nothing but unifies code; it does not make the
                 * slave detect less corruptions).
                 */
                numberOfEventTypes = format_description_event - 1;

                postHeaderLengths[start_event_v3 - 1] = start_v3_header_len;
                postHeaderLengths[query_event - 1] = query_header_minimal_len;
                postHeaderLengths[rotate_event - 1] = rotate_header_len;
                postHeaderLengths[load_event - 1] = load_header_length;
                postHeaderLengths[create_file_event - 1] = create_file_header_length;
                postHeaderLengths[append_block_event - 1] = append_block_header_len;
                postHeaderLengths[exec_load_event - 1] = exec_load_header_len;
                postHeaderLengths[delete_file_event - 1] = delete_file_header_len;
                postHeaderLengths[new_load_event - 1] = postHeaderLengths[load_event - 1];
                break;

            case 1: /* 3.23 */
                /*
                 * We build an artificial (i.e. not sent by the master) event,
                 * which describes what those old master versions send.
                 */
                serverVersion = "3.23";
                commonHeaderLength = old_header_len;

                /*
                 * The first new event in binlog version 4 is Format_desc. So
                 * any event logType after that does not exist in older versions.
                 * We use the events known by version 3, even if version 1 had
                 * only a subset of them (this is not a problem: it uses a few
                 * bytes for nothing but unifies code; it does not make the
                 * slave detect less corruptions).
                 */
                numberOfEventTypes = format_description_event - 1;

                postHeaderLengths[start_event_v3 - 1] = start_v3_header_len;
                postHeaderLengths[query_event - 1] = query_header_minimal_len;
                postHeaderLengths[load_event - 1] = load_header_length;
                postHeaderLengths[create_file_event - 1] = create_file_header_length;
                postHeaderLengths[append_block_event - 1] = append_block_header_len;
                postHeaderLengths[exec_load_event - 1] = exec_load_header_len;
                postHeaderLengths[delete_file_event - 1] = delete_file_header_len;
                postHeaderLengths[new_load_event - 1] = postHeaderLengths[load_event - 1];
                break;

            default:
                numberOfEventTypes = 0;
                commonHeaderLength = 0;
        }
    }

    public static FormatDescriptionLogEvent getFormatDescription(final int binlogVersion) throws IOException
    {
        /* identify binlog format */
        switch (binlogVersion)
        {
            case 4: /* mysql 5.0 */
                return format_description_event_5_x;
            case 3:
                return format_description_event_4_0_x;
            case 1:
                return format_description_event_3_23;
            default:
                throw new IOException("Unknown binlog version: " + binlogVersion);
        }
    }

    public static void doServerVersionSplit(String serverVersion, int[] versionSplit)
    {
        String[] split = serverVersion.split("\\.");
        if (split.length < 3)
        {
            versionSplit[0] = 0;
            versionSplit[1] = 0;
            versionSplit[2] = 0;
        } else
        {
            int j = 0;
            for (int i = 0; i <= 2; i++)
            {
                String str = split[i];
                for (j = 0; j < str.length(); j++)
                {
                    if (Character.isDigit(str.charAt(j)) == false)
                    {
                        break;
                    }
                }
                if (j > 0)
                {
                    versionSplit[i] = Integer.valueOf(str.substring(0, j), 10);
                } else
                {
                    versionSplit[0] = 0;
                    versionSplit[1] = 0;
                    versionSplit[2] = 0;
                }
            }
        }
    }

    public static long versionProduct(int[] versionSplit)
    {
        return ((versionSplit[0] * 256 + versionSplit[1]) * 256 + versionSplit[2]);
    }

    public void calcServerVersionSplit()
    {
        doServerVersionSplit(serverVersion, serverVersionSplit);
    }

    public long getVersionProduct()
    {
        return versionProduct(serverVersionSplit);
    }

    public boolean isVersionBeforeChecksum()
    {
        return getVersionProduct() < checksumVersionProduct;
    }

    public final int getCommonHeaderLength()
    {
        return commonHeaderLength;
    }

    public final short[] getPostHeaderLengths()
    {
        return postHeaderLengths;
    }

}
